/**
 * @Date: Mar 4, 2010 8:02:29 PM
 */
package com.philip.journal.home.service.util;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.log4j.Logger;

/**
 * Converts a java bean, either a Map or a normal bean into a map, applying customisation: changing the
 * property name to another.
 *
 * @author cry30
 */
public class BeanToMapConverter {

    /** RTFC. */
    private final Logger logger = Logger.getLogger(getClass()); // NOPMD by r39 on 3/30/11 2:54 PM

    /** Map containing the property name substitution for converting java bean to Map. */
    private transient Map<String, String> fieldMapping;

    /** Map containing the constant value to be used for a given property name. */
    private transient Map<String, String> fixedMapping;

    /**
     * Converts a list of java bean into as list of key value paired map.
     *
     * @param list - the list of java bean to be converted.
     * @return The converted List of Map. Returns null if passed with a null parameter.
     */
    public final List<Map<String, Object>> convert(final List<?> list) {
        List<Map<String, Object>> retval = null;
        if (list != null) {
            retval = new ArrayList<Map<String, Object>>();
            for (final Object object : list) {
                retval.add(convert(object));
            }
        }
        return retval;
    }

    /**
     * Applies the property name conversion of the specified bean or Map to the resulting Map. Mapping is
     * configured in the spring configuration file. serviceContext.xml
     *
     * @param bean - the java bean to convert into a Map.
     * @return The converted java bean into a Map of properties to values.
     */
    public final Map<String, Object> convert(final Object bean) {
        final Map<String, Object> retval = new HashMap<String, Object>();
        final String[] properties = bean instanceof Map ? getMapKeys(bean) : getBeanProperties(bean);

        for (final String property : properties) {
            try {
                final Object value = PropertyUtils.getProperty(bean, property);
                final String keyToUse = fieldMapping == null || fieldMapping.get(property) == null ? property
                        : fieldMapping.get(property);
                retval.put(keyToUse, value);
            } catch (final Exception ignore) {
                logger.warn(ignore.getMessage(), ignore);
            }
        }
        if (fixedMapping != null) {
            for (final String nextKey : fixedMapping.keySet()) {
                Object value = fixedMapping.get(nextKey);
                if ("false".equals(value) || "true".equals(value)) {
                    value = Boolean.parseBoolean(value.toString());
                }
                retval.put(nextKey, value);
            }
        }
        return retval;
    }

    /** Marks the first letter of the property name. */
    private static final int PROPFL_START = 3;

    /** Marks the 2nd letter of the property name. */
    private static final int PROPFL_END = 4;

    /**
     * Extracts all java bean properties through the accessor methods.
     *
     * @param bean - the java bean whose to extract properties from.
     * @return Array of String for the extracted bean properties. Will return an empty array if no property
     *         can be derived from the bean.
     */
    private String[] getBeanProperties(final Object bean) {
        final List<String> retval = new ArrayList<String>();
        for (final Method next : bean.getClass().getDeclaredMethods()) {
            if (next.getName().startsWith("get") && !next.getName().equals("getClass")) {
                try {
                    if (next.getReturnType().isPrimitive()
                            || !next.getReturnType().getPackage().getName().startsWith("com")) {
                        retval.add(next.getName().substring(PROPFL_START, PROPFL_END).toLowerCase()
                                + next.getName().substring(PROPFL_END));
                    }
                } catch (final Exception e) {
                    logger.debug(next.getName(), e);
                }
            }
        }
        return retval.toArray(new String[retval.size()]);
    }

    /**
     * Gets the keys of map as an Array of String.
     *
     * @param bean - Map to extract keys from.
     * @return String array of the keys of the bean.
     * @throws IllegalArgumentException when the bean passed is null or is not an instance of Map.
     */
    @SuppressWarnings("unchecked")
    private String[] getMapKeys(final Object bean) {
        if (!(bean instanceof Map)) {
            throw new IllegalArgumentException();
        }

        final Map<Object, Object> map = (Map<Object, Object>) bean;
        final List<String> retval = new ArrayList<String>();
        for (final Object object : map.keySet()) {
            retval.add(object.toString());
        }
        return retval.toArray(new String[retval.size()]);
    }

    /**
     * @param pFieldMapping - @see {@link #fieldMapping}.
     */
    public final void setFieldMapping(final Map<String, String> pFieldMapping) {
        this.fieldMapping = pFieldMapping;
    }

    /**
     * @param pfixedMapping - @see {@link #fieldMapping}.
     */
    public final void setFixedMapping(final Map<String, String> pfixedMapping) {
        this.fixedMapping = pfixedMapping;
    }
}
